/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using Xceed.Silverlight.Data.Stats;

namespace Xceed.Silverlight.Data
{
  public abstract class SimpleAsyncIndexDataSourceProvider : AsyncIndexDataSourceProvider
  {
    #region Static Fields

    private static readonly Guid ExecuteDataQueryCallId = DataUtils.CreateCallId();
    private static readonly Guid ExecuteCountQueryCallId = DataUtils.CreateCallId();
    private static readonly Guid ExecuteCountSingleGroupQueryCallId = DataUtils.CreateCallId();
    private static readonly Guid ExecuteFindQueryCallId = DataUtils.CreateCallId();
    private static readonly Guid ExecuteGroupCountQueryCallId = DataUtils.CreateCallId();
    private static readonly Guid ExecuteSingleGroupCountQueryCallId = DataUtils.CreateCallId();
    private static readonly Guid ExecuteStatQueryCallId = DataUtils.CreateCallId();
    private static readonly Guid ExecuteSingleGroupStatQueryCallId = DataUtils.CreateCallId();

    #endregion

    #region Constructor

    protected SimpleAsyncIndexDataSourceProvider()
      : base()
    {
      this.InitializeFeatures();
    }

    #endregion

    #region CanFilterGroup Property

    public override bool CanFilterGroup
    {
      get
      {
        return m_canFilterGroup;
      }
    }

    private bool m_canFilterGroup; //false

    #endregion

    #region CanFind Property

    public override bool CanFind
    {
      get
      {
        return m_canFind;
      }
    }

    private bool m_canFind; //false

    #endregion

    #region CanGroupCount Property

    public override bool CanGroupCount
    {
      get
      {
        return m_canGroupCount;
      }
    }

    private bool m_canGroupCount; //false

    #endregion

    #region CanCalculateStats Property

    public override bool CanCalculateStats
    {
      get
      {
        return m_canCalculateStats;
      }
    }

    private bool m_canCalculateStats; //false

    #endregion

    protected abstract IAsyncResult BeginCount( AsyncCallback callback, object state );

    protected abstract long EndCount( IAsyncResult asyncResult );

    protected virtual IAsyncResult BeginCountSingleGroup(
      IEnumerable<FieldValue> fieldValues,
      AsyncCallback callback,
      object state )
    {
      throw new NotSupportedException();
    }

    protected virtual long EndCountSingleGroup( IAsyncResult asyncResult )
    {
      throw new NotSupportedException();
    }

    protected abstract IAsyncResult BeginGetRange(
      long index,
      int count,
      AsyncCallback callback,
      object state );

    protected abstract IEnumerable EndGetRange( IAsyncResult asyncResult );

    protected abstract IAsyncResult BeginGetSortedRange(
      IEnumerable<SortDescription> sortOrder,
      long index,
      int count,
      AsyncCallback callback,
      object state );

    protected abstract IEnumerable EndGetSortedRange( IAsyncResult asyncResult );

    protected virtual IAsyncResult BeginGetGroupCount(
      IEnumerable<SortDescription> sortOrder,
      long index,
      int count,
      AsyncCallback callback,
      object state )
    {
      throw new NotSupportedException();
    }

    protected virtual IEnumerable<long> EndGetGroupCount( IAsyncResult asyncResult )
    {
      throw new NotSupportedException();
    }

    protected virtual IAsyncResult BeginGetSingleGroupCount(
      IEnumerable<SortDescription> sortOrder,
      IEnumerable<FieldValue> fieldValues,
      long index,
      int count,
      AsyncCallback callback,
      object state )
    {
      throw new NotSupportedException();
    }

    protected virtual IEnumerable<long> EndGetSingleGroupCount( IAsyncResult asyncResult )
    {
      throw new NotSupportedException();
    }

    protected virtual IAsyncResult BeginGetStatResult(
      StatFunction statFunction,
      AsyncCallback callback,
      object state )
    {
      throw new NotSupportedException();
    }

    protected virtual StatResult EndGetStatResult( IAsyncResult asyncResult )
    {
      throw new NotSupportedException();
    }

    protected virtual IAsyncResult BeginGetSingleGroupStatResult(
      StatFunction statFunction,
      IEnumerable<FieldValue> fieldValues,
      AsyncCallback callback,
      object state )
    {
      throw new NotSupportedException();
    }

    protected virtual StatResult EndGetSingleGroupStatResult( IAsyncResult asyncResult )
    {
      throw new NotSupportedException();
    }

    protected virtual IAsyncResult BeginFind(
      IEnumerable<SortDescription> sortOrder,
      IEnumerable<FindValue> fieldValues,
      FindPosition position,
      AsyncCallback callback,
      object state )
    {
      throw new NotSupportedException();
    }

    protected virtual FindResult EndFind( IAsyncResult asyncResult )
    {
      throw new NotSupportedException();
    }

    protected sealed override IAsyncResult BeginExecuteCountQuery(
      CountQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      AsyncResult asyncResult;

      if( queryParameters.FilterGroup != null )
      {
        IEnumerable<FieldValue> fieldValues = this.GetFieldValues(
                                                queryParameters.Sort.SortDescriptions,
                                                queryParameters.FilterGroup.Values ).ToList();

        asyncResult = new AsyncResult( SimpleAsyncIndexDataSourceProvider.ExecuteCountSingleGroupQueryCallId, callback, state );
        asyncResult.Start(
          ( cb, st ) => this.BeginCountSingleGroup( fieldValues, cb, st ),
          null );
      }
      else
      {
        asyncResult = new AsyncResult( SimpleAsyncIndexDataSourceProvider.ExecuteCountQueryCallId, callback, state );
        asyncResult.Start(
          ( cb, st ) => this.BeginCount( cb, st ),
          null );
      }

      return asyncResult;
    }

    protected sealed override CountQueryResult EndExecuteCountQuery( IAsyncResult asyncResult )
    {
      long count;
      IAsyncResult childResult = asyncResult.GetLastAsyncResult<IAsyncResult>();

      if( asyncResult.UseCallId( SimpleAsyncIndexDataSourceProvider.ExecuteCountSingleGroupQueryCallId ) )
      {
        count = this.EndCountSingleGroup( childResult );
      }
      else
      {
        asyncResult.ValidateCallEnd( SimpleAsyncIndexDataSourceProvider.ExecuteCountQueryCallId );

        count = this.EndCount( childResult );
      }

      return new CountQueryResult( count );
    }

    protected sealed override IAsyncResult BeginExecuteDataQuery(
      DataQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      // Retrieve the query parameters.
      IEnumerable<SortDescription> sortOrder = ( queryParameters.Sort != null ) ? queryParameters.Sort.SortDescriptions : null;
      long index = ( queryParameters.Skip != null ) ? queryParameters.Skip.Count : 0;
      int count = ( queryParameters.Take != null ) ? queryParameters.Take.Count : int.MaxValue;

      // Check out if a sort order was specified.
      bool useDefaultSortOrder = ( sortOrder == null );

      // Retrieve the appropriate method to call to execute the query.
      Func<AsyncCallback, object, IAsyncResult> beginGetRange;
      if( useDefaultSortOrder )
      {
        beginGetRange = ( cb, st ) => this.BeginGetRange( index, count, cb, st );
      }
      else
      {
        beginGetRange = ( cb, st ) => this.BeginGetSortedRange( sortOrder, index, count, cb, st );
      }

      ContextAsyncResult asyncResult = new ContextAsyncResult(
                                         SimpleAsyncIndexDataSourceProvider.ExecuteDataQueryCallId,
                                         callback,
                                         state );
      // Set the context in order to call the appropriate method to retrieve the result.
      asyncResult.CallContext = useDefaultSortOrder;

      asyncResult.Start( beginGetRange, null );

      return asyncResult;
    }

    protected sealed override DataQueryResult EndExecuteDataQuery( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( SimpleAsyncIndexDataSourceProvider.ExecuteDataQueryCallId );

      ContextAsyncResult contextAsyncResult = ( ContextAsyncResult )asyncResult;
      IAsyncResult childAsyncResult = contextAsyncResult.LastIAsyncResult;
      bool useDefaultSortOrder = ( bool )contextAsyncResult.CallContext;
      IEnumerable dataList;

      if( useDefaultSortOrder )
      {
        dataList = this.EndGetRange( childAsyncResult );
      }
      else
      {
        dataList = this.EndGetSortedRange( childAsyncResult );
      }

      return new DataQueryResult( dataList );
    }

    protected sealed override IAsyncResult BeginExecuteGroupCountQuery(
      GroupCountQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      AsyncResult asyncResult;

      IEnumerable<SortDescription> sortOrder = queryParameters.Sort.SortDescriptions;
      long index = ( queryParameters.Skip != null ) ? queryParameters.Skip.Count : 0;
      int length = queryParameters.Take.Count;

      if( queryParameters.FilterGroup != null )
      {
        IEnumerable<FieldValue> fieldValues = this.GetFieldValues(
                                                queryParameters.Sort.SortDescriptions,
                                                queryParameters.FilterGroup.Values ).ToList();

        asyncResult = new AsyncResult( SimpleAsyncIndexDataSourceProvider.ExecuteSingleGroupCountQueryCallId, callback, state );
        asyncResult.Start(
          ( cb, st ) => this.BeginGetSingleGroupCount( sortOrder, fieldValues, index, length, cb, st ),
          null );
      }
      else
      {
        asyncResult = new AsyncResult( SimpleAsyncIndexDataSourceProvider.ExecuteGroupCountQueryCallId, callback, state );
        asyncResult.Start(
          ( cb, st ) => this.BeginGetGroupCount( sortOrder, index, length, cb, st ),
          null );
      }

      return asyncResult;
    }

    protected sealed override GroupCountQueryResult EndExecuteGroupCountQuery( IAsyncResult asyncResult )
    {
      IEnumerable<long> groupCount;
      IAsyncResult childResult = asyncResult.GetLastAsyncResult<IAsyncResult>();

      if( asyncResult.UseCallId( SimpleAsyncIndexDataSourceProvider.ExecuteSingleGroupCountQueryCallId ) )
      {
        groupCount = this.EndGetSingleGroupCount( childResult );
      }
      else
      {
        asyncResult.ValidateCallEnd( SimpleAsyncIndexDataSourceProvider.ExecuteGroupCountQueryCallId );

        groupCount = this.EndGetGroupCount( childResult );
      }

      return new GroupCountQueryResult( groupCount );
    }

    protected sealed override IAsyncResult BeginExecuteStatQuery(
      StatQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      StatFunction statFunction = queryParameters.Function.StatFunction;
      AsyncResult asyncResult;

      if( queryParameters.FilterGroup != null )
      {
        IEnumerable<FieldValue> fieldValues = this.GetFieldValues(
                                                queryParameters.Sort.SortDescriptions,
                                                queryParameters.FilterGroup.Values ).ToList();

        asyncResult = new AsyncResult( SimpleAsyncIndexDataSourceProvider.ExecuteSingleGroupStatQueryCallId, callback, state );
        asyncResult.Start(
          ( cb, st ) => this.BeginGetSingleGroupStatResult( statFunction, fieldValues, cb, st ),
          null );
      }
      else
      {
        asyncResult = new AsyncResult( SimpleAsyncIndexDataSourceProvider.ExecuteStatQueryCallId, callback, state );
        asyncResult.Start(
          ( cb, st ) => this.BeginGetStatResult( statFunction, cb, st ),
          null );
      }

      return asyncResult;
    }

    protected sealed override StatQueryResult EndExecuteStatQuery( IAsyncResult asyncResult )
    {
      StatResult result;
      IAsyncResult childResult = asyncResult.GetLastAsyncResult<IAsyncResult>();

      if( asyncResult.UseCallId( SimpleAsyncIndexDataSourceProvider.ExecuteSingleGroupStatQueryCallId ) )
      {
        result = this.EndGetSingleGroupStatResult( childResult );
      }
      else
      {
        asyncResult.ValidateCallEnd( SimpleAsyncIndexDataSourceProvider.ExecuteStatQueryCallId );

        result = this.EndGetStatResult( childResult );
      }

      return new StatQueryResult( result );
    }

    protected sealed override IAsyncResult BeginExecuteFindQuery(
      FindQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      Debug.Assert( ( queryParameters.Sort != null ) && ( queryParameters.Find != null ) );

      // Retrieve the query parameters.
      IEnumerable<SortDescription> sortOrder = queryParameters.Sort.SortDescriptions;
      IEnumerable<FindValue> fieldValues = queryParameters.Find.Values;
      FindPosition position = queryParameters.Find.Position;

      AsyncResult asyncResult = new AsyncResult(
                                  SimpleAsyncIndexDataSourceProvider.ExecuteFindQueryCallId,
                                  callback,
                                  state );

      asyncResult.Start(
        ( cb, st ) => this.BeginFind( sortOrder, fieldValues, position, cb, st ),
        null );

      return asyncResult;
    }

    protected sealed override FindQueryResult EndExecuteFindQuery( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( SimpleAsyncIndexDataSourceProvider.ExecuteFindQueryCallId );

      IAsyncResult childAsyncResult = asyncResult.GetLastAsyncResult<IAsyncResult>();
      FindResult result = this.EndFind( childAsyncResult );

      return new FindQueryResult( result );
    }

    private IEnumerable<FieldValue> GetFieldValues(
      IEnumerable<SortDescription> sortOrder,
      IEnumerable<FilterGroupValue> values )
    {
      return sortOrder.Zip( values, ( sort, value ) => new FieldValue( sort.PropertyName, value.Value ) );
    }

    private void InitializeFeatures()
    {
      Type dataSourceProviderType = this.GetType();
      bool groupLevelCountImplemented = DataUtils.IsMethodImplemented(
                                          DataUtils.GetMethodDefinition<IEnumerable<FieldValue>, AsyncCallback, object, IAsyncResult>(
                                            ( fieldValues, callback, state ) => this.BeginCountSingleGroup( fieldValues, callback, state ) ),
                                          dataSourceProviderType );
      bool allLevelGroupCountImplemented = DataUtils.IsMethodImplemented(
                                             DataUtils.GetMethodDefinition<IEnumerable<SortDescription>, long, int, AsyncCallback, object, IAsyncResult>(
                                               ( sortOrder, index, count, callback, state ) => this.BeginGetGroupCount( sortOrder, index, count, callback, state ) ),
                                             dataSourceProviderType );
      bool groupLevelGroupCountImplemented = DataUtils.IsMethodImplemented(
                                               DataUtils.GetMethodDefinition<IEnumerable<SortDescription>, IEnumerable<FieldValue>, long, int, AsyncCallback, object, IAsyncResult>(
                                                 ( sortOrder, fieldValues, index, count, callback, state ) => this.BeginGetSingleGroupCount( sortOrder, fieldValues, index, count, callback, state ) ),
                                               dataSourceProviderType );
      bool allLevelStatImplemented = DataUtils.IsMethodImplemented(
                                       DataUtils.GetMethodDefinition<StatFunction, AsyncCallback, object, IAsyncResult>(
                                         ( statFunction, callback, state ) => this.BeginGetStatResult( statFunction, callback, state ) ),
                                       dataSourceProviderType );
      bool groupLevelStatImplemented = DataUtils.IsMethodImplemented(
                                         DataUtils.GetMethodDefinition<StatFunction, IEnumerable<FieldValue>, AsyncCallback, object, IAsyncResult>(
                                           ( statFunction, fieldValues, callback, state ) => this.BeginGetSingleGroupStatResult( statFunction, fieldValues, callback, state ) ),
                                         dataSourceProviderType );
      bool findImplemented = DataUtils.IsMethodImplemented(
                               DataUtils.GetMethodDefinition<IEnumerable<SortDescription>, IEnumerable<FindValue>, FindPosition, AsyncCallback, object, IAsyncResult>(
                                 ( sortOrder, findValues, position, callback, state ) => this.BeginFind( sortOrder, findValues, position, callback, state ) ),
                               dataSourceProviderType );

      m_canFind = findImplemented;
      m_canGroupCount = m_canFind && allLevelGroupCountImplemented;
      m_canCalculateStats = allLevelStatImplemented;

      m_canFilterGroup = ( groupLevelCountImplemented )
                      && ( !m_canGroupCount || groupLevelGroupCountImplemented )
                      && ( !m_canCalculateStats || groupLevelStatImplemented );
    }
  }
}
